using System;
using System.Collections.Generic;
using System.Text;

namespace ElteIk.RoboSoccer.Core
{
    /// <summary>
    /// A base class for a general game which needs a physical engine.
    /// It supports running the game logic in a separate thread.
    /// </summary>
    public class Game
	{
		#region Private and protected fields

		protected Physics.IPhysicsEngine physicsEngine;

		protected System.Threading.Thread gameThread;

		#endregion 

		#region Lifecycle methdos

		public Game()
		{
            time = 0;
            timeLimit = 0;
            stepInterval = 20;
            stepLength = 0.020f;
		}
		
		#endregion

		#region Public methods

        /// <summary>
        /// Initializes the game with a physics engine loaded from the configuration file.
        /// </summary>
        public virtual void Initialize()
        {
            Initialize(LoadEngine());
        }

        /// <summary>
        /// Initializes the game with a physics engine given as a parameter.
        /// </summary>
        /// <param name="engine"></param>
        public virtual void Initialize(ElteIk.RoboSoccer.Core.Physics.IPhysicsEngine engine)
        {
            this.physicsEngine = engine;
            physicsEngine.Clear();
            if (gameThread != null && gameThread.IsAlive)
            {
                gameThread.Abort();
                gameThread.Join();
            }
            //gameThread = new System.Threading.Thread(new System.Threading.ThreadStart(UpdaterThreadMain));
            //gameThread.Name = "RoboSoccer Game Logic Thread";
            //gameThread.IsBackground = true;
        }

        protected virtual void Reset()
        {
            if (physicsEngine == null || stopRequested || restartRequested)
            {
                Initialize();
            }
            //else if (stopRequested || restartRequested)
            //{
            //    Initialize(physicsEngine);
            //}
            stopRequested = false;
            restartRequested = false;
            time = 0;
        }

        /// <summary>
        /// Starts the game - as a seperate thread.
        /// </summary>
		public virtual void Start()
		{
            //if (physicsEngine == null)
            //    physicsEngine = LoadEngine();

            //Initialize(physicsEngine);

            Reset();

            if (gameThread == null || gameThread.ThreadState == System.Threading.ThreadState.Stopped)
            {
                gameThread = new System.Threading.Thread(new System.Threading.ThreadStart(UpdaterThreadMain));
                gameThread.Name = "RoboSoccer Game Logic Thread";
                gameThread.IsBackground = true;
                gameThread.Start();
            }
            else if (gameThread.ThreadState != System.Threading.ThreadState.Running && gameThread.ThreadState != System.Threading.ThreadState.Background)
            {
                gameThread.Start();
            }
        }

        /// <summary>
        /// Starts the game with timelimit and blocking the calling thread. 
        /// There will be no additional updater thread created.         
        /// </summary>
        /// <param name="timeLimit">Timelimit must be supplied in order to guarantee termination.</param>
        public virtual void Run(decimal timeLimit)
        {
            Reset();
            this.timeLimit = timeLimit;
            OnGameStarted();
            while (time < timeLimit)
            {
                Step(stepLength);
            }
            OnTimeOut();
            OnGameStopped();
        }

        /// <summary>
        /// Steps the simulation by f.
        /// </summary>
        /// <param name="f"></param>
		public virtual void Step(float f)
		{
            //physicsEngine.SynchronizeWithGame();
			physicsEngine.Step(f);
			OnUpdated();
        }

        public virtual void Restart()
        {
            Stop();
            Start();
        }

        ///// <summary>
        ///// Pauses the game. (Brute-force)
        ///// </summary>
        //public virtual void Pause()
        //{
        //    if (gameThread.ThreadState == System.Threading.ThreadState.Running)
        //    {
        //        gameThread.Suspend();
        //    }
        //}

        ///// <summary>
        ///// Continues the updating cycle.
        ///// </summary>
        //public virtual void Resume()
        //{
        //    if (gameThread.ThreadState == System.Threading.ThreadState.Suspended)
        //    {
        //        gameThread.Resume();
        //    }
        //}

        private bool stopRequested = false;
        private bool restartRequested = false;

        /// <summary>
        /// Stops the game thread and updating cycle. After this the game cannot be continued but only restarted.
        /// </summary>
        public virtual void Stop()
        {
            stopRequested = true;
        }

        public virtual void StopAndWait()
        {
            stopRequested = true;
            if (gameThread != null && gameThread.ThreadState == System.Threading.ThreadState.Background)
                gameThread.Join();
        }

        public bool IsRunning
        {
            get
            {
                return (gameThread != null && ((gameThread.ThreadState == System.Threading.ThreadState.Background
                                            || gameThread.ThreadState == System.Threading.ThreadState.Running) && !stopRequested));
            }
        }

        /// <summary>
        /// Synchronizes the physics engine with the states of the physical objects in the game core.
        /// 
        /// It must be called explicitly because the game has no rights to set the physical state of an object
        /// it can only tell the engine what forces are effective to the object.
        /// 
        /// Calling this method will force the engine to update its state.
        /// </summary>
        public virtual void SynchronizePhysics()
        {
            physicsEngine.SynchronizeWithGame();
        }

		#endregion


		/// <summary>
		/// Loads the physics engine defined in the application's config file.
		/// It was created to support loose coupling.
		/// </summary>
		protected Physics.IPhysicsEngine LoadEngine()
		{
            if (this.physicsEngine != null)
                return this.physicsEngine;

            string engineName = System.Configuration.ConfigurationManager.AppSettings["PhysicsEngineAssembly"];
            if (string.IsNullOrEmpty(engineName))
                engineName = "RoboSoccer.Core.PhysicsEngine";
            Type physicsEngineInterfaceType = typeof(Physics.IPhysicsEngine);

            Physics.IPhysicsEngine physicsEngine = null;

            try
            {

                if (!string.IsNullOrEmpty(engineName))
                {
                    System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(engineName);
                    // Catch the first one that implements IPhysicsEngine
                    // TODO: Add support to change physics engine by class name
                    foreach (Type exportedType in assembly.GetExportedTypes())
                    {
                        if (!exportedType.IsClass)
                            continue;
                        if (exportedType.GetInterface(physicsEngineInterfaceType.Name) != null)
                        {
                            Physics.IPhysicsEngine engine = (Physics.IPhysicsEngine)Activator.CreateInstance(exportedType);
                            physicsEngine = engine;
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw new ApplicationException("Cannot load physics engine. Please refer to the manual regarding this error.");
            }

            return physicsEngine;
		}

		#region Timing support

        /// <summary>
        /// The main method of the updater thread.
        /// </summary>
		protected virtual void UpdaterThreadMain()
		{
            OnGameStarted();
			while ((timeLimit == 0m || time < timeLimit) && !stopRequested)
			{
				System.Threading.Thread.Sleep(stepInterval);
                lock (this)
                {
                    Step(stepLength);
                }
			}
            if (timeLimit != 0m && time >= timeLimit)
            {
                OnTimeOut();
            }

            OnGameStopped();
        }

        protected decimal time;

        /// <summary>
        /// The game time.
        /// </summary>
        public decimal Time
        {
            get
            {
                return time;
            }

            set
            {
                time = value;
            }
        }

        protected int stepInterval;

        /// <summary>
        /// The time in milliseconds that must elapse between two steps;
        /// </summary>
        public int StepInterval
        {
            get { return stepInterval; }
            set { stepInterval = value; }
        }

        protected float stepLength;

        /// <summary>
        /// The duration of one step in seconds.
        /// It is recommended to synchronize this value with stepInterval.
        /// </summary>
        public float StepLength
        {
            get { return stepLength; }
            set { stepLength = value; }
        }

        protected decimal timeLimit;

        /// <summary>
        /// The maximum time the game will run without being stopped.
        /// </summary>
        public decimal TimeLimit
        {
            get { return timeLimit; }
            set { timeLimit = value; }
        }

		#endregion

		#region Events

        /// <summary>
        /// Fires when the game was updated.
        /// </summary>
		public event EventHandler Updated;
		protected virtual void OnUpdated()
		{
			if (Updated != null)
                Updated(this, EventArgs.Empty);
		}

        /// <summary>
        /// Fires when the game gets started.
        /// </summary>
        public event EventHandler GameStarted;

        protected void OnGameStarted()
        {
            if (GameStarted != null)
                GameStarted(this, EventArgs.Empty);
        }

        /// <summary>
        /// Fires when the game gets stopped.
        /// </summary>
        public event EventHandler GameStopped;

        protected void OnGameStopped()
        {
            if (GameStopped != null)
                GameStopped(this, EventArgs.Empty);
        }

        /// <summary>
        /// Fires when the game stops because of timeout.
        /// </summary>
        public event EventHandler TimeOut;

        protected void OnTimeOut()
        {
            if (TimeOut != null)
                TimeOut(this, EventArgs.Empty);
        }

		#endregion
	}
}
